#include "kernel.h"

#define ARG0(tf) (tf->regs.eax)
#define ARG1(tf) (tf->regs.ebx)
#define ARG2(tf) (tf->regs.ecx)
#define ARG3(tf) (tf->regs.edx)
#define ARG4(tf) (tf->regs.esi)
#define ARG5(tf) (tf->regs.edi)
#define RET(tf)  (tf->regs.eax)
#define SYSCALL(fn, tf) static void sc_##fn(struct trapframe_t *tf)

/* The implementation of syscall is following the description in
 * inc/syscall.h */

SYSCALL(kprintf, tf)
{
	 const char *fmt = (const char *)ARG1(tf);
	 va_list va = (va_list)ARG2(tf);

     vkprintf(fmt, va);
}

SYSCALL(send, tf)
{
	 uint32_t	ap	     = ARG1(tf);
	 /* TODO send level checking */
	 uint32_t ips = ARG2(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 
	 uint32_t	arg_lo	 = ARG3(tf);
	 uint32_t   arg_hi   = ARG4(tf);

	 uint64_t   payload = ((uint64_t)(arg_hi) << 32) | arg_lo;
	 RET(tf) = ips_send(ap, ips, payload);
}

SYSCALL(send_try, tf)
{
	 uint32_t ips = ARG1(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 uint32_t wait = ARG2(tf);

	 RET(tf) = ips_send_try(ips, wait);
}

SYSCALL(send_break, tf)
{
	 uint32_t ips = ARG1(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 RET(tf) = ips_send_break(ips);
}

SYSCALL(wait_pretend, tf)
{
	 RET(tf) = sch_wait_pretend(WLEVEL_USER);
}

SYSCALL(wait_try, tf)
{
	 if ((RET(tf) = sch_wait_try()) == -E_YIELD)
		  call_yield();
}

SYSCALL(wait, tf)
{
	 if ((RET(tf) = sch_wait(WLEVEL_USER)) == -E_YIELD)
		  call_yield();
}

SYSCALL(notify, tf)
{
	 uint32_t pid = ARG1(tf);
	 /* TODO the perm checking */
	 /* if (procs[pid].env != procs[cur_proc_id()].env) */
	 /* 	  RET(tf) = -E_INVAL; */
	 /* else */
	 {
		  RET(tf) = sch_notify(pid, WLEVEL_USER);
	 }
}

SYSCALL(set_proc_attr, tf)
{
	 uint32_t index = ARG1(tf);
	 uint32_t value = ARG2(tf);

	 switch (index)
	 {
	 case PA_SIGNAL_FLAGS:
		  RET(tf) = proc_set_signal_flags(value);
		  break;
	 case PA_SIGNAL_HANDLER:
		  RET(tf) = proc_set_signal_handler(value);
		  break;
	 default:
		  RET(tf) = -E_INVAL;
		  break;
	 }
}

SYSCALL(signal, tf)
{
	 uint32_t proc = ARG1(tf);
	 RET(tf) = proc_signal(proc, SIG_USER);
}

SYSCALL(exit, tf)
{
	 uint32_t ret = ARG1(tf);
	 uint64_t info = EX_SYS_KILL | ((cur_proc_id() & 0xffff) << 16) | ((uint64_t)ret << 32);
	 // kprintf("payload = %08x%08x\n", (uint32_t)(info >> 32), (uint32_t)info);
	 send_exception(cur_proc_id(), info, WLEVEL_EXIT);

	 kprintf("PANIC: ZOMBIE %d!\n", cur_proc_id());
	 monitor(NULL);
}

SYSCALL(recv, tf)
{
	 uint32_t ap = ARG1(tf);
	 /* TODO recv level checking */
	 uint32_t ips = ARG2(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 RET(tf) = ips_recv(ap, ips);
}

SYSCALL(recv_try, tf)
{
	 uint32_t ips = ARG1(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 uint32_t wait     = ARG2(tf);
	 uint64_t payload;
	 
	 if ((RET(tf) = ips_recv_try(ips, &payload, wait)) == 0)
	 {
		  ARG1(tf) = (uint32_t)payload;
		  ARG2(tf) = (uint32_t)(payload >> 32);

		  // kprintf("recv payload = %08x%08x\n", ARG2(tf), ARG1(tf));
	 }
}

SYSCALL(recv_break, tf)
{
	 uint32_t ips = ARG1(tf) & (PROCESS_IPS_COUNT - 1);
	 if (ips >= PROCESS_IPS_COUNT - PROCESS_IPS_RESERVED)
	 {
		  RET(tf) = -E_INVAL;
		  return;
	 }
	 ips = (cur_proc_id() << PROCESS_IPS_COUNT_SHIFT) | ips;
	 RET(tf) = ips_recv_break(ips);
}

SYSCALL(proc_fork, tf)
{
	 uint32_t proc = ARG1(tf);
	 uint32_t code_entry = ARG2(tf);
	 uint32_t stack_entry = ARG3(tf);

	 uint32_t eip = tf->eip;
	 uint32_t stk = tf->stk.esp;
	 uint32_t ebp = tf->regs.ebp;
	 
	 tf->eip = code_entry;
	 tf->stk.esp = stack_entry;
	 tf->regs.ebp = 0;
	 RET(tf) = 0;

	 RET(tf) = proc_fork(tf, proc);

	 tf->eip = eip;
	 tf->stk.esp = stk;
	 tf->regs.ebp = ebp;
	 
	 if (RET(tf) < 0)
		  kprintf("%d fork(%d): %e\n", cur_proc_id(), proc, tf->regs.eax);
}

SYSCALL(proc_kill, tf)
{
	 uint32_t proc = ARG1(tf);
	 RET(tf) = proc_kill(proc);
}

SYSCALL(env_create, tf)
{
	 uint32_t   flags       = ARG1(tf);
	 uint32_t   priority    = ARG2(tf);
	 uint32_t   code_entry  = ARG3(tf);
	 uint32_t   stack_entry = ARG4(tf);
	 int        ex_ap       = ARG5(tf);

	 physaddr_t vpt = vmem_vpt_new();
	 if (vpt == 0)
	 {
		  RET(tf) = -E_NO_MEM;
		  return;
	 }

	 uint32_t eip = tf->eip;
	 uint32_t stk = tf->stk.esp;
	 uint32_t ebp = tf->regs.ebp;

	 tf->eip = code_entry;
	 tf->stk.esp = stack_entry;
	 tf->regs.ebp = 0;
	 RET(tf) = 0;

	 uint32_t init_flags = 0;
	 uint32_t init_eflags = 0;

	 init_flags  = PFLAG_VALID;
	 init_eflags = flags;
	 
	 RET(tf) = env_create(tf, vpt, init_flags, init_eflags, priority, ex_ap);

	 tf->eip = eip;
	 tf->stk.esp = stk;
	 tf->regs.ebp = ebp;
	 
	 if (RET(tf) < 0)
	 {
		  /* TODO clear the allocated page */
		  kprintf("%d env_create(%08x): %e\n", cur_proc_id(), vpt, tf->regs.eax);
	 }
}

SYSCALL(env_free, tf)
{
	 uint32_t env = ARG1(tf);
	 RET(tf) = -E_UNSPECIFIED;
}

SYSCALL(proc_attach, tf)
{
	 uint32_t proc = ARG1(tf);
	 spl_acquire(&procs[proc].lock);
	 RET(tf) = sch_attach(proc);
	 spl_release(&procs[proc].lock);
}

SYSCALL(proc_detach, tf)
{
	 uint32_t proc = ARG1(tf);
	 spl_acquire(&procs[proc].lock);
	 RET(tf) = sch_detach(proc);
	 spl_release(&procs[proc].lock);
}

SYSCALL(proc_invoke, tf)
{
	 uint32_t proc = ARG1(tf);
	 uint32_t lcpu = ARG2(tf);

	 /* TODO */
	 RET(tf) = -E_UNSPECIFIED;
}

SYSCALL(proc_invoke_all, tf)
{
	 uint32_t procs_block = ARG1(tf);

	 /* TODO */
	 RET(tf) = -E_UNSPECIFIED;
}

SYSCALL(mmap_set, tf)
{
	 uint32_t proc  = ARG1(tf);
	 uint32_t vaddr = ARG2(tf);
	 uint32_t paddr = ARG3(tf);
	 uint32_t perm  = ARG4(tf);
	 uint32_t pte;
	 
	 if (!auth_is_userspace_range(vaddr, PAGE_SIZE))
	 {
		  RET(tf) = -E_INVAL;
	 }
	 else
	 {
		  int st = paddr == 0 ? -1 : pmem_page_status(paddr);
		  if (st != PPAGE_MANAGED && st != -1)
		  {
			   RET(tf) = -E_INVAL;
		  }
		  else
		  {
			   if (paddr == 0)
					pte = 0;
			   else pte = PTE_ADDR(paddr) | PTE_P |
						 ((perm & SC_MMAP_SET_PERM_WRITE) ? PTE_W : 0) | PTE_U;

			   int to_clear = 0;
			   physaddr_t rvpd_p = envs[procs[proc].env].vpt;
			   physaddr_t rvpt_p;
			   
			   uint32_t *tvpt;
			   BEGIN_TPAGE((physaddr_t)PAGE_ALIGN(rvpd_p), tvpt);
		  
			   if ((rvpt_p = tvpt[PAGEDIR_IDX(vaddr)]) & PTE_P)
					rvpt_p = PTE_ADDR(rvpt_p);
			   else
			   {
					if (pmem_page_alloc(1, &rvpt_p, 0, PPAGE_CORE) < 0)
						 RET(tf) = -E_NO_MEM;
					else
					{
						 tvpt[PAGEDIR_IDX(vaddr)] = rvpt_p | PTE_U | PTE_W | PTE_P;
						 to_clear = 1;
					}
			   }
			   
			   END_TPAGE;

			   if (rvpt_p)
			   {
					BEGIN_TPAGE(rvpt_p, tvpt);
					
					if (to_clear)
						 memset(tvpt, 0, PAGE_SIZE);
					tvpt[PAGETAB_IDX(vaddr)] = pte;
					
					END_TPAGE;
					RET(tf) = 0;
			   }
		  }
	 }
}

SYSCALL(ppage_alloc, tf)
{
	 uint32_t num = ARG1(tf);
	 uint32_t dma = ARG2(tf);

	 physaddr_t result;
	 if ((RET(tf) = pmem_page_alloc(num, &result, dma, PPAGE_MANAGED)) == 0)
		  ARG1(tf) = result;
}

SYSCALL(ppage_free, tf)
{
	 uint32_t paddr = ARG1(tf);
	 pmem_page_free(paddr);
	 RET(tf) = 0;
}

SYSCALL(set_soft_timer, tf)
{
	 uint32_t alarm = ARG1(tf);
	 int pid        = ARG2(tf);

	 set_soft_timer(alarm, pid);
	 RET(tf) = 0;
}

SYSCALL(notify_ex, tf)
{
	 uint32_t pid = ARG1(tf);
	 uint32_t lev = ARG2(tf);
	 RET(tf) = sch_notify(pid, lev);
}

static ex_handler_t sc_table[SC_MAX + 1] =
{
	 [SC_KPRINTF] = &sc_kprintf,

	 [SC_SEND]			  = &sc_send,
	 [SC_SEND_TRY]		  = &sc_send_try,
	 [SC_SEND_BREAK]	  = &sc_send_break,
	 [SC_WAIT_PRETEND]    = &sc_wait_pretend,
	 [SC_WAIT_TRY]        = &sc_wait_try,
	 [SC_WAIT]            = &sc_wait,
	 [SC_NOTIFY]		  = &sc_notify,
	 [SC_SET_PROC_ATTR]   = &sc_set_proc_attr,
	 [SC_SIGNAL]		  = &sc_signal,
	 [SC_EXIT]            = &sc_exit,

	 [SC_RECV]		   = &sc_recv,
	 [SC_RECV_TRY]	   = &sc_recv_try,
	 [SC_RECV_BREAK]   = &sc_recv_break,

     [SC_PROC_FORK]		  = &sc_proc_fork,
	 [SC_PROC_KILL]		  = &sc_proc_kill,
	 [SC_ENV_CREATE]	  = &sc_env_create,
	 [SC_ENV_FREE]		  = &sc_env_free,
	 [SC_PROC_ATTACH]     = &sc_proc_attach,
	 [SC_PROC_DETACH]     = &sc_proc_detach,
	 [SC_PROC_INVOKE]	  = &sc_proc_invoke,
	 [SC_PROC_INVOKE_ALL] = &sc_proc_invoke_all,
	 [SC_MMAP_SET]        = &sc_mmap_set,
	 [SC_PPAGE_ALLOC]	  = &sc_ppage_alloc,
	 [SC_PPAGE_FREE]	  = &sc_ppage_free,
	 [SC_SET_SOFT_TIMER]  = &sc_set_soft_timer,
	 [SC_NOTIFY_EX]       = &sc_notify_ex,
};

void
syscall_handler(struct trapframe_t tf)
{
	 if (tf.num > SC_MAX)
     {
		  tf.regs.eax = -E_UNSPECIFIED;
     }
     else
     {
		  // kprintf("begin syscall %02x %08x %08x %08x %08x %08x\n", tf.num, tf.regs.ebx, tf.regs.ecx, tf.regs.edx, tf.regs.esi, tf.regs.edi);
		  sc_table[tf.num](&tf);
		  // kprintf("end syscall %02x\n", tf.num);
	 }
}
